#pragma once
#include <iostream>
#include <cstdlib> //cstdlib->stdlib.h
#include <vector>
#include <unistd.h>
#include <sys/wait.h>
#include "Task.hpp" //任务模块
using namespace std;

//.hpp后缀可以使得方法实现和声明在一个文件，这样Main.cc在调用时只需要包含.hpp头文件

// 基于匿名管道的进程池

// 对（信道）管道进行管理
// 先描述：描述管道信息
class channel
{
public:
    channel(int fd, pid_t id)
        : _wfd(fd),
          _subid(id)
    {
        // 使用to_string将fd和id都转成字符加到后面作为名字一部分
        _name = "channel-" + to_string(_wfd) + "-" + to_string(_subid);
    }

    ~channel() {}

    // 通过信道发送任务信息给子进程
    void Send(int code)
    {
        int n = write(_wfd, &code, sizeof(code));
        (void)n; // 对n做一下使用，可以绕过编译器对未使用变量的检测
    }

    // 关闭_wfd写端fd的方法，这样进而使得子进程退出读，进而exit进程退出
    void Close()
    {
        close(_wfd);
    }

    // 等待回收进程的方法
    void Wait()
    {
        // 不需要退出码
        pid_t rid = waitpid(_subid, nullptr, 0);
        (void)rid;
    }

    // 外部需要拿到channel内部的信息，我们需要一些get方法
    int Fd() { return _wfd; }
    pid_t SubId() { return _subid; }
    string Name() { return _name; }

private:
    // 每个管道都需要有对应的文件描述符
    int _wfd;
    // 还需要知道对应的子进程的id
    pid_t _subid;
    // 以及为了之后方便打印提示消息对应管道的名字
    string _name;
};

// 要创建多少个进程池
const int gdefaultnum = 5;

// 再组织；管理信道（管道）的接口类，我们这里通过vector数据结构来组织
class ChannelManager
{
public:
    ChannelManager()
        : _next(0)
    {
    }

    ~ChannelManager() {}

    void Insert(int wfd, pid_t subid)
    {
        // 构建一个channel然后push到组织的vector数组中(先描述，再组织)
        // channel c(wfd, subid);
        //_channels.push_back(move(c)); //c为临时对象，应该使用右值引用减少拷贝
        // 或者也可以使用vector容器中提供的emplace_back方法
        // 直接调用内部构造函数帮我们创建一个对象在插入vector中
        _channels.emplace_back(wfd, subid);
    }

    // 打印_channels中各channel的信息的方法
    void PrintChannels()
    {
        for (auto &channel : _channels)
        {
            cout << channel.Name() << endl;
        }
    }

    // 轮询选择一个信道的方法
    channel &Select()
    {
        auto &c = _channels[_next];
        _next++;
        _next %= _channels.size(); // 防止_next越界
        return c;
    }

    // 结束所有进程的方法
    // void StopSubProcess()
    // {
    //     for (auto &channel : _channels)
    //     {
    //         // 需要channel中提供一个关闭fd的方法
    //         channel.Close();
    //         cout << "关闭：" << channel.Name() << endl;
    //     }
    // }

    // 回收所有子进程的方法
    // void WaitSubProcess()
    // {
    //     for (auto &channel : _channels)
    //     {
    //         // 需要channel中提供一个进程等待回收的方法
    //         channel.Wait();
    //         cout << "回收：" << channel.Name() << endl;
    //     }
    // }

    void CloseAndWait()
    {
        // 如果这样合并起来的话会有bug，也就是main中说的隐藏较深的bug
        // 在Main.cc中已经解释的很详细了
        //  for (auto &channel : _channels)
        //  {
        //     channel.Close();
        //     channel.Wait();
        //  }

        // 合并起来的解决方案1：倒着来关闭回收
        for (int i = _channels.size() - 1; i >= 0; i--)
        {
            _channels[i].Close();
            cout << "关闭：" << _channels[i].Name() << endl;
            _channels[i].Wait();
            cout << "回收：" << _channels[i].Name() << endl;
        }
    }

private:
    // 通过vector来组织
    vector<channel> _channels;
    int _next; // 轮询选择信道
};

// 进程池类
class ProcessPool
{
public:
    ProcessPool(int num)
        : _process_num(num)
    {
        // 构造时把任务注册好
        _tm.Register(PrintLog);
        _tm.Register(DownLoad);
        _tm.Register(UpLoad);
    }

    ~ProcessPool() {}

    // 子进程要做的工作
    void Work(int rfd)
    {
        while (true)
        {
            // 以4个字节读取管道内容
            int code = 0;
            ssize_t n = read(rfd, &code, sizeof(code));
            if (n > 0)
            {
                // 正常读成功
                // 先要判断以下读取的内容是否规范，也就是是不是按照4个字节来读
                if (n != sizeof(code))
                {
                    continue; // 表示让程序直接进入下一个循环直到读的是规范的
                }

                // 子进程要做的工作
                cout << "子进程[" << getpid() << "]要执行的任务码是：" << code << endl;
                // 执行读到的任务码对应的任务
                // 这个执行任务的方法是管理任务模块中的Execte方法
                _tm.Execute(code);
            }
            else if (n == 0)
            {
                // 说明父进程关闭了写端，子进程也要退出
                cout << "子进程退出" << endl;
                break;
            }
            else
            {
                // 读取失败
                cout << "读取错误" << endl;
                break;
            }
        }
    }

    // 启动进程池方法
    bool Start()
    {
        // 要启动process_num个进程
        for (int i = 0; i < _process_num; i++)
        {
            // 1. 创建管道
            int pipefd[2] = {0};
            int n = pipe(pipefd);
            if (n < 0)
            {
                return false;
            }

            // 2. 创建子进程
            // 子进程读，父进程写
            int subid = fork();
            if (subid < 0)
                return false;
            else if (subid == 0)
            {
                // 子进程
                // 3. 关闭写端
                close(pipefd[1]);
                Work(pipefd[0]); // 子进程要做的读端相关工作
                close(pipefd[0]);

                // 执行完直接退出
                // 不会干扰for循环，只有父进程会一直循环创建执行循环内代码
                exit(0);
            }
            else
            {
                // 父进程
                // 3. 关闭读端
                close(pipefd[0]);
                // 需要在ChannelManager内部把我们的fd和subid信息传入创建的channel中
                // 再由ChannelManager把创建好的channel插入到数组中
                // 所以在ChannelManager类中需要Insert方法来完成上述操作
                // 其中我们父进程对应的fd文件描述符是写端：pipefd[1]
                _cm.Insert(pipefd[1], subid);
            }
        }
        // 启动成功
        return true;
    }

    // Debug方法中查看每个channel信道（管道）的信息
    void Debug()
    {
        // 调用ChannelManager中的PrintChannels方法
        _cm.PrintChannels();
    }

    void Run()
    {
        // 这里就是选择一个channel，然后通过write写入
        // 告知子进程要执行的任务码
        // 父进程得雨露均沾的分配任务给子进程！

        // 1. 选择一个任务，这个选择权在管理任务的模块的Code方法中
        // Code会返回一个随机选择的任务
        int taskcode = _tm.Code();

        // 2. 选择一个信道[子进程]，负载均衡的选择一个子进程，完成任务
        // 我们这里采用轮询的策略来实现均衡
        auto &c = _cm.Select();
        cout << "选择了一个子进程：" << c.Name() << endl;

        // 3. 发送任务
        c.Send(taskcode);
        cout << "发送了一个任务码：" << taskcode << endl;
    }

    void Stop()
    {
        // 只需要关闭我们父进程所有的wfd即可
        // 因为关闭写端，子进程读端读到\0返回值为0，上面已经写了返回值为0时结束子进程Work
        // 进而代码运行到exit(0)子进程退出

        // fd和进程id都被ChannelManager中的vector管理着
        // 所以我们得在ChannelManager中写一个方法来结束所有的进程
        //_cm.StopSubProcess();

        // 而后得进程等待，需要回收所有的子进程
        // 所以我们得在ChannelManager中写一个方法来回收子进程
        //_cm.WaitSubProcess();

        // 合并起来
        _cm.CloseAndWait();
    }

private:
    // 进程池类内部需要包含我们的通信信道
    // 所以我们通过管理通信信道的类创建一个对象在进程池内部
    ChannelManager _cm;
    // 要创建的进程池对应的进程的个数，这样就能确定管道的个数
    int _process_num;
    // 还需要有一个任务管理模块的对象
    TaskManager _tm;
};
